package modellstadt.geom;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import processing.core.PGraphics;
import processing.core.PMatrix3D;
import processing.core.PVector;

public class Face extends AbstractFace {
	public PVector[] vertices = new PVector[0];
	public int id;
	public int type;
	public boolean end;

	public Face() {
	}
	public Face(PVector v1, PVector v2, PVector v3) {
		vertices = new PVector[] { v1, v2, v3 };
	}

	public Face(PVector v1, PVector v2, PVector v3, PVector v4) {
		vertices = new PVector[] { v1, v2, v3, v4 };
	}

	public Face(PVector[] vertices) {
		this.vertices = vertices;
	}

	public Face copy(){
		PVector[] copyvertices=new PVector[vertices.length];
		for (int i=0;i<copyvertices.length;i++){
			copyvertices[i]=vertices[i].get();
		}
		return new Face(copyvertices);
	}
	public void makeIndependant(){
		for (int i=0;i<this.getNumVertices();i++){
			vertices[i]=vertices[i].get();
		}
	}
	public int size() {
		return vertices.length;
	}

	public PVector get(int i) {
		return vertices[i];
	}

	public PVector set(int i, PVector v) {
		vertices[i] = v;
		return v;
	}

	public void shift(int shift) {
		PVector[] newVertices = new PVector[vertices.length];
		for (int i = 0; i < vertices.length; i++) {
			int iOld = i + shift;
			if (iOld < 0)
				iOld = vertices.length + iOld;
			else if (iOld > vertices.length - 1)
				iOld = iOld % vertices.length;
			newVertices[i] = vertices[iOld];
		}
		vertices = newVertices;
	}

	public void reverse() {

		for (int i = 0; i < vertices.length / 2; i++) {
			PVector temp = vertices[i];
			vertices[i] = vertices[vertices.length - i - 1];
			vertices[vertices.length - i - 1] = temp;
		}
	}
	boolean isVerticalRect(){
		// face constists only of horizontal edges or vertical edges
		if (getNumVertices()!=4)return false;
		for (int i = 0; i <this.getNumVertices() ; i++) {
			PVector p1=get(i);
			PVector p2=get(i+1);
			if (p1.z!=p2.z&&(p1.x!=p2.x||p2.y!=p1.y)) return false;
		} 
		return true;
	}
	public boolean isHorizontal(){
		float z=get(0).z;
		for (int i = 0; i <this.getNumVertices() ; i++) {
			if (z!=get(i).z)return false;
		}
		return true;
	}
	public PVector normal() {
		PVector v = PVector.sub(get(1), get(0));
		PVector u = PVector.sub(get(2), get(0));
		PVector normal = new PVector();
		PVector.cross(v, u, normal);
		normal.normalize();
		return normal;
	}

	public float getArea2D() {
		float area = 0;
		int j;
		for (int i = 0; i < vertices.length; i++) {
			j = i + 1;
			if (j == vertices.length)
				j = 0;
			PVector p1 = vertices[i];
			PVector p2 = vertices[j];
			area += (p1.x + p2.x) * (p2.y - p1.y);
		}
		return area / 2f;
	}

	public void addNode(PVector n) {
		vertices = Arrays.copyOf(vertices, vertices.length + 1);
		vertices[vertices.length - 1] = n;
		// TODO Auto-generated method stub

	}

	public PVector getNode(int j) {
		// TODO Auto-generated method stub
		return vertices[j];
	}

	public int getNNodes() {
		// TODO Auto-generated method stub
		return vertices.length;
	}

	@Override
	public int getNumVertices() {
		// TODO Auto-generated method stub
		return vertices.length;
	}

	@Override
	public void getPosition(int i, float[] coords) {
		// TODO Auto-generated method stub
		PVector v = get(i);
		coords[0] = v.x;
		coords[1] = v.y;
		coords[2] = v.z;
	}

	@Override
	public void setPosition(int i, float x, float y, float z) {
		// TODO Auto-generated method stub
		PVector v = get(i);
		v.set(x, y, z);

	}

	public Face[] pointLoftCenter(float l) {
		PVector normal = normal();
		normal.mult(l);
		PVector center = this.getCenter();
		center.add(normal);
		return pointLoft(center);
	}

	public Face[] pointLoft(PVector v) {
		Face[] result = new Face[this.getNumVertices()];
		for (int i = 0; i < getNumVertices(); i++) {
			PVector v1 = get(i);
			PVector v2 = get((i + 1) % getNumVertices());
			result[i] = new FaceTri(v1, v2, v);
		}
		return result;
	}

	public ArrayList<Face> extrude(float extrudeFactor) {
		PVector normal = normal();
		normal.mult(extrudeFactor);
		return extrude(normal);
	}

	public ArrayList<Face> extrude(PVector[] extrudes) {
		ArrayList<Face> result = new ArrayList<Face>(getNumVertices() + 1);
		PVector[] newPts = new PVector[getNumVertices()];
		for (int i = 0; i < getNumVertices(); i++) {
			newPts[i] = PVector.add(getNode(i), extrudes[i]);
		}
		for (int i = 0; i < getNumVertices(); i++) {
			int i2 = i + 1;
			i2 = i2 % getNumVertices();
			PVector v1 = getNode(i);
			PVector v2 = getNode(i2);
			PVector v3 = newPts[i2];
			PVector v4 = newPts[i];
			FaceQuad face = new FaceQuad(v1, v2, v3, v4);
			result.add(face);
		}
		Face top = new Face(newPts);
		result.add(top);
		return result;
	}

	public ArrayList<Face> extrude(PVector extrudeVector) {
		PVector[] extrudes = new PVector[getNumVertices()];
		for (int i = 0; i < extrudes.length; i++) {
			extrudes[i] = extrudeVector;
		}
		return extrude(extrudes);
	}
	public ArrayList<Face> extrudeMulti( int nSteps,float stepLength,boolean closeTop) {
		ArrayList<Face>result=new ArrayList<Face>();
		PVector[][]grid=new PVector[nSteps][getNumVertices()]; 
		PVector normal=normal();
		normal.setMag(stepLength);
		PVector addV=new PVector();
		for (int i=0;i<nSteps;i++){
		
			for (int j=0;j<getNumVertices();j++){
				grid[i][j]=get(j).get();
				grid[i][j].add(addV);
			}
			addV.add(normal);
		}
		for (int i=0;i<nSteps-1;i++){
			for (int j=0;j<grid[i].length;j++){
				int j2=(j+1)%grid[i].length;
				result.add(new Face(grid[i][j],grid[i][j2],grid[i+1][j2],grid[i+1][j]));
			}
		}
		if (closeTop){
			Face top=new Face(grid[nSteps-1]);
			top.reverse();
			result.add(top);
		}
		return result;
	}

	public Face offset(float offset) {
		PVector[] newPts = new PVector[getNumVertices()];
		for (int i = 0; i < getNumVertices(); i++) {
			int iP = i - 1;
			if (iP < 0)
				iP = getNumVertices() - 1;
			int iN = (i + 1) % getNumVertices();
			PVector v0 = getNode(iP);
			PVector v1 = getNode((i));
			PVector v2 = getNode(iN);
			newPts[i] = (getOffsetPoint(v0, v1, v2, offset, offset));
		}
		return new Face(newPts);
	}

	public Face offset(float[] offset) {
		PMatrix3D pm=this.getMatrixXY();
		this.transform(pm);
		pm.invert();
		PVector[] newPts = new PVector[getNumVertices()];
		for (int i = 0; i < this.getNumVertices(); i++) {
			int iP = i - 1;
			if (iP < 0)
				iP = getNumVertices() - 1;
			int iN = (i + 1) % getNumVertices();
			PVector v0 = getNode(iP);
			PVector v1 = getNode((i));
			PVector v2 = getNode(iN);
			newPts[i] = (getOffsetPoint(v0, v1, v2, offset[iP], offset[i]));
			
		}
		Face result= new Face(newPts);
		result.transform(pm);
		this.transform(pm);
		return new Face(newPts);
	}

	public PVector getEdgeVector(int i) {
		PVector v1 = get(i);
		PVector v2 = get(i + 1);
		return PVector.sub(v2, v1);
	}
	public ArrayList<Face> splitEdgeMinDist(int iEdge, float minD) {
		PVector v = getEdgeVector(iEdge);
		int nS=(int)(v.mag()/minD);
		return splitEdge(iEdge, nS);
	}

	public ArrayList<Face> splitEdge(int iEdge, int nSegments) {
		if (nSegments<=0){
			ArrayList<Face>result=new ArrayList<Face>();
			result.add(this);
			return result;
		}
		PVector v = get(iEdge);
		PVector e = getEdgeVector(iEdge);
		e.div(nSegments);
		PVector[] splitPoints = new PVector[nSegments-1];
		for (int i = 1; i < nSegments; i++) {
			splitPoints[i - 1] = new PVector(v.x + i * e.x, v.y + i * e.y, v.z
					+ i * e.z);
		}
		e.normalize();
		return splitByPlanes(splitPoints, e);
	}
	public ArrayList<Face> splitByPlanes(ArrayList<PVector>pts, PVector normal) {
		PVector[]points=new PVector[pts.size()];
		for (int i=0;i<points.length;i++){
			points[i]=pts.get(i);
		}
		return splitByPlanes(points,normal);
	}
	public ArrayList<Face> splitByPlanes(PVector[] pts, PVector normal) {
		ArrayList<Face> result = new ArrayList<Face>();
		ArrayList<Face> temp;
		Face face = this;
		for (int i = 0; i < pts.length; i++) {
			Plane plane = new Plane(pts[i], normal);
			temp = face.splitConvexFace(plane);
			result.add(temp.get(1));
			face = temp.get(0);
		}
		face.shift(2);
		result.add(face);
		return result;
	}

	// splits a face with a line defined by a and b
	// if we split a convex face (like rectangle, no concave corners), there
	// should be max 2 intersections
	// if there are two intersections we make 2 new faces.
	// otherwise there is no intersection and we return the original face
	public ArrayList<Face> splitConvexFace(Plane plane) {
		int indexIntersect = 0;
		int nPts = this.getNNodes();
		PVector[] intersections = new PVector[2];
		int[] indexes = new int[2];
		for (int i = 0; i < nPts; i++) {
			PVector p1 = get(i);
			PVector p2 = get((i + 1) % nPts);
			PVector intersection = plane.segmentPlaneIntersection(p1, p2);
			if (intersection != null) {
				intersections[indexIntersect] = intersection;
				indexes[indexIntersect] = (i + 1) % nPts;
				indexIntersect++;
			}
		}
		ArrayList<Face> newFaces = new ArrayList<Face>();
		if (indexIntersect == 2) {
			Face f1 = new Face();
			int cI = indexes[0];
			int end = indexes[1];
			f1.addNode(intersections[0]);
			while (cI != end) {
				f1.addNode(get(cI));
				cI++;
				cI = cI % nPts;
			}
			f1.addNode(intersections[1]);
			Face f2 = new Face();
			cI = indexes[1];
			end = indexes[0];
			f2.addNode(intersections[1]);
			while (cI != end) {
				f2.addNode(get(cI));
				cI++;
				cI = cI % nPts;
			}
			f2.addNode(intersections[0]);
			newFaces.add(f1);
			newFaces.add(f2);
		} else {
			newFaces.add(this);
		}
		return newFaces;
	}
	public ArrayList<Face> splitQuadRel(float[] splitU,float[] splitV) {
		int nU=splitU.length;
		int nV=splitV.length;
		PVector p0=this.get(0);
		PVector p1=this.get(1);
		PVector p2=this.get(2);
		PVector p3=this.get(3);
		PVector[][]grid=new PVector[nU][nV];
		for (int u=0;u<nU;u++){
			PVector u1=getPointBetweenRelative(p0, p1, splitU[u]);
			PVector u2=getPointBetweenRelative(p3, p2, splitU[u]);
			for (int v=0;v<nV;v++){
				grid[u][v]=getPointBetweenRelative(u1, u2, splitV[v]);
			}
		}
		ArrayList<Face>result=new ArrayList<Face>();
		for (int x = 0; x < nU-1; x++) {
			for (int y = 0; y < nV-1; y++) {
				result.add(new FaceQuad(grid[x][y + 1], grid[x + 1][y + 1],
						grid[x + 1][y], grid[x][y]));
			}
		}
		return result;
	}
	public ArrayList<Face> splitQuadAbs(float[] splitU,float[] splitV) {
		float[]splitURel=new float[splitU.length];
		float[]splitVRel=new float[splitV.length];
		float p01=this.get(0).dist(get(1));
		float p12=this.get(1).dist(get(2));
		for (int i=0;i<splitURel.length;i++){
			splitURel[i]=splitU[i]/p01;
		}
		for (int i=0;i<splitVRel.length;i++){
			splitVRel[i]=splitV[i]/p12;
		}
		return splitQuadRel(splitURel,splitVRel);
	}
	public ArrayList<Face> splitQuadAbs(float[] splitU) {
		return splitQuadAbs(splitU,new float[]{0,get(2).dist(get(1))});		
	}
	public ArrayList<Face> splitOffset(float offset) {
		float[] offsets = new float[size()];
		Arrays.fill(offsets, offset);
		return splitOffset(offsets);
	}

	public ArrayList<Face> splitOffset(float offset, int i) {
		float[] offsets = new float[size()];
		Arrays.fill(offsets, 0);
		offsets[i] = offset;
		return splitOffset(offsets);
	}

	public ArrayList<Face> splitOffset(float[] offset) {
		
		ArrayList<Face> result = new ArrayList<Face>();
		Face offsetFace = offset(offset);
		result.add(offsetFace);
		for (int i = 0; i < size(); i++) {
			if (Math.abs(offset[i]) > 0) {
				int i2 = (i + 1) % size();
				result.add(new FaceQuad(get(i), get(i2), offsetFace.get(i2),
						offsetFace.get(i)));
			}
		}
		for (int i = 0; i < result.size(); i++) {
			Face face = result.get(i);
			if (face.getArea2D() < 0) {
				face.reverse();
			}
			
		}
		
		return result;
	}

	public ArrayList<Face> splitRhythm (float r1, float r2){
		ArrayList<Face> result = new ArrayList<Face>();
		ArrayList<PVector> points1 = new ArrayList<PVector>();
		ArrayList<PVector> points2 = new ArrayList<PVector>();
		
		boolean rSwitch=true;
		float magCheck=r1;
		
		PVector v0=get(0);
		PVector v1=get(1);
		PVector v2=get(2);
		PVector v3=get(3);	
		
		points1.add(v0);
		points2.add(v3);
		
		float mag = v1.dist(v2);
		while (mag>magCheck){
			PVector v01 = getPointBetweenRelative(v0,v1, (magCheck*1f / mag*1f));
			PVector v32 = getPointBetweenRelative(v3,v2, (magCheck*1f / mag*1f));
			points1.add(v01);
			points2.add(v32);
			
			mag = v01.dist(v1);
			v0 = v01;
			v3 = v32;
			
			
			if (rSwitch){
				magCheck=r2;
				rSwitch = !rSwitch;
			}
			else{
				magCheck=r1;
				rSwitch = !rSwitch;
			}
		}
		
		points1.add(v1);
		points2.add(v2);
		
		for (int i=0;i<points1.size()-1;i++){
			Face newFace=new Face(points1.get(i), points1.get(i+1), points2.get(i+1), points2.get(i));
			result.add(newFace);
		}
		
		return result;
	}
	
	
	public ArrayList<Face> split1D(int nX){
		ArrayList<Face> result = split2D(nX,1);
		return result;
	}
 
	public ArrayList<Face> split2D(int nX, int nY) {
		ArrayList<Face> result = new ArrayList<Face>();
		PVector[][] grid = new PVector[nX + 1][nY + 1];
		grid[0] = getPointsBetween(get(0), get(1), nY);
		grid[nX] = getPointsBetween(get(3), get(2), nY);
		for (int x = 1; x < grid.length - 1; x++) {
			for (int y = 0; y < grid[x].length; y++) {
				grid[x][y] = getPointBetweenRelative(grid[0][y], grid[nX][y], x
						* 1f / nX);
			}
		}

		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				result.add(new FaceQuad(grid[x][y + 1], grid[x + 1][y + 1],
						grid[x + 1][y], grid[x][y]));
			}
		}
		return result;
	}


	public void displayNormal3D(PGraphics g, float l) {
		// TODO Auto-generated method stub
		PVector n = normal();
		n.mult(l);
		PVector c = this.getCenter();
		g.line(c.x, c.y, c.z, c.x + n.x, c.y + n.y, c.z + n.z);
	}
	@Override
	public Face getFace(int i) {
		// TODO Auto-generated method stub
		return this;
	}
	@Override
	public int getNumFaces() {
		// TODO Auto-generated method stub
		return 1;
	}
	
}
